package org.feng.redis.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.Duration;
import java.time.temporal.ChronoUnit;

/**
 * Redis 缓存服务<br>
 * 注意：需要配置扫描包 "org.feng.redis.config"<br>
 * 以及以下配置：
 * <pre>
 *     spring
 *       redis:
 *          host: localhost
 *          port: 0000
 *          password: 123
 * </pre>
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2022年08月26日 13时56分
 */
@Component
@Slf4j
public class RedisCacheService {
    @Resource
    private ReactiveRedisTemplate<String, Object> reactiveRedisTemplate;

    /**
     * 设置存储键、值
     *
     * @param key   键
     * @param value 值
     */
    public void setValue(String key, Object value) {
        // 存储key-value数据
        reactiveRedisTemplate.opsForValue().set(key, value).subscribe();
    }

    /**
     * 通过键获取值
     *
     * @param key 键
     * @return 值
     */
    public Mono<Object> getValue(String key) {
        // 根据key查询value
        return reactiveRedisTemplate.opsForValue().get(key);
    }

    public void setValueWithExp(String key, String value, Duration timeout) {
        reactiveRedisTemplate.opsForValue().set(key, value, timeout).subscribe();
    }

    /**
     * 设置键-值，并且增加失效时间，单位为小时。
     *
     * @param key             键
     * @param value           值
     * @param expireHoursTime 失效小时数
     */
    public void setValueWithHoursTimeExp(String key, String value, Long expireHoursTime) {
        if (expireHoursTime == null || expireHoursTime <= 0) {
            throw redisError("失效时间（单位：小时）不能小于0");
        }
        // 存储key-value数据，并设置时间，单位秒
        setValueWithExp(key, value, Duration.of(expireHoursTime, ChronoUnit.HOURS));
    }

    /**
     * 设置键-值，并且增加失效时间，单位为天。
     *
     * @param key            键
     * @param value          值
     * @param expireDaysTime 失效天数
     */
    public void setValueWithDaysTimeExp(String key, String value, Long expireDaysTime) {
        if (expireDaysTime == null || expireDaysTime <= 0) {
            throw redisError("失效时间（单位：天）不能小于0");
        }
        // 存储key-value数据，并设置时间，单位秒
        setValueWithExp(key, value, Duration.of(expireDaysTime, ChronoUnit.DAYS));
    }


    /**
     * 设置键-值，并且增加失效时间，单位为秒。
     *
     * @param key               键
     * @param value             值
     * @param expireSecondsTime 失效秒数
     */
    public void setValueWithSecondsTimeExp(String key, String value, Long expireSecondsTime) {
        if (expireSecondsTime == null || expireSecondsTime <= 0) {
            throw redisError("失效时间（单位：秒）不能小于0");
        }
        // 存储key-value数据，并设置时间，单位秒
        setValueWithExp(key, value, Duration.of(expireSecondsTime, ChronoUnit.SECONDS));
    }

    /**
     * 设置键-值，并且增加失效时间，单位为毫秒。
     *
     * @param key              键
     * @param value            值
     * @param expireMillisTime 失效毫秒数
     */
    public void setValueWithMillisTimeExp(String key, String value, Long expireMillisTime) {
        if (expireMillisTime == null || expireMillisTime <= 0) {
            throw redisError("失效时间（单位：毫秒）不能小于0");
        }
        // 存储key-value数据，并设置时间，单位毫秒
        setValueWithExp(key, value, Duration.of(expireMillisTime, ChronoUnit.MILLIS));

    }

    /**
     * 设置键-值，并且增加失效时间，单位为分钟。
     *
     * @param key               键
     * @param value             值
     * @param expireMinutesTime 失效分钟数
     */
    public void setValueWithMinutesTimeExp(String key, String value, Long expireMinutesTime) {
        if (expireMinutesTime == null || expireMinutesTime <= 0) {
            throw redisError("失效时间（单位：分钟）不能小于0");
        }
        // 存储key-value数据，并设置时间，单位分钟
        setValueWithExp(key, value, Duration.of(expireMinutesTime, ChronoUnit.MINUTES));
    }

    /**
     * 如果不存在当前的键，则将键值存储，并返回true；若存在当前键，则不做操作，返回false
     *
     * @param key   键
     * @param value 值
     * @return 当前键不存在，存储当前键值，返回true
     */
    public Mono<Boolean> setIfAbsent(String key, Object value) {
        return reactiveRedisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 如果不存在当前的键，则将键值存储，并返回true；若存在当前键，则不做操作，返回false<br>
     * 并且设置失效时间
     *
     * @param key      键
     * @param value    值
     * @param duration 失效时间，在指定时间之后，redis会自动删除该键值
     * @return 当前键不存在，存储当前键值，返回true
     */
    public Mono<Boolean> setIfAbsentWithTimeOut(String key, Object value, Duration duration) {
        return reactiveRedisTemplate.opsForValue().setIfAbsent(key, value, duration);
    }

    /**
     * 查询是否存在键
     *
     * @param key 键
     * @return 存在时返回true；否则为false
     */
    public Mono<Boolean> hasKey(String key) {
        return reactiveRedisTemplate.hasKey(key);
    }

    private RuntimeException redisError(String message) {
        return new RuntimeException(message);
    }

    /**
     * 测试redis
     */
    @PostConstruct
    private void testRedis() {
        setValueWithSecondsTimeExp("test_feng123", "666789", 30L);
        getValue("test_feng123").doOnNext(val -> log.info("redis -> {}", val)).subscribe();
        setValueWithMinutesTimeExp("4343232", "", 1L);
        setIfAbsent("4343232", 33333L).doOnSuccess(val -> log.info("redis setIfAbsent-> {}", val)).subscribe();
        setIfAbsent("43432323333333333333", 33333L).doOnSuccess(val -> log.info("redis setIfAbsent-> {}", val)).subscribe();
        setIfAbsentWithTimeOut("434323233333333333334", 33333L, Duration.of(10, ChronoUnit.SECONDS)).doOnSuccess(val -> log.info("redis setIfAbsentTime-> {}", val)).subscribe();
    }
}
